from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _
from datetime import datetime

###############################################################################################
#
#                       CELIBAX USER

###############################################################################################

# this represent an user. a user can bet in many betting rooms. The representation of the user in a given betting room is a player
# will be the association of an account holder and of an bet room so that a account holder can be a player in many bet rooms.
# it is associated to a user (login, pswd) 
class UserProfile(models.Model):
        user = models.ForeignKey(User, unique=True)
        sendMeEmails = models.BooleanField(default=True) # indicate if the user wish toi receives emails
        # the account is associated to a user but it is not enough to identify a Player who is the combination of a UserProfile and a betroom
        # when login, we need to select a player (and so a betroom) the following field is used on this purpose
        nameOfTheLastVistedBetRoom = models.CharField(max_length=20,default="none")
        # The contract market can be quite complicated for the player needs to anderstand the bid/offer principle.
        # When chosing a simplified market, the player will only be allowed to make bid and hit other people bid.
        # The offer linked to his liquidity token will have a price of 100
        useSimplifiedContractMkt = models.BooleanField(default=True)
        def __unicode__(self):
		return self.user.username + " last logged in " + self.nameOfTheLastVistedBetRoom

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.get_or_create(user=instance)

post_save.connect(create_user_profile, sender=User)


###############################################################################################
#
#                       BET ROOM

###############################################################################################
# this represent a betting room. For now there is just one
class BetRoom(models.Model):
        roomType = models.IntegerField( default = 0 )# 0: by repartition, 1: by digital contract
        personInCharge = models.ForeignKey(User) # account holder in charge of the administration of the bet room 
        name = models.CharField(max_length=20, unique=True)
        status = models.IntegerField(default=1) # the status are the following:
        # -1: being lauched, asset need validation
        # 0: being lauched
        # 1: running
        # 2: suspended
        description = models.TextField()#description of what is traded is in this bet room
        password = models.CharField(_('password'), max_length=128)#Users will need this password to create a player in this betroom
        allowPlayerToSpeak = models.BooleanField(default=True) # do we create the box to send message in the javascript
        
        def __unicode__(self):
		return self.name +' (administrator is '+self.personInCharge.username+')'


	

###############################################################################################
#
#                       MARKET BY PERCENTAGE

###############################################################################################
# Assets are what bets are about. We distinguish between asset we can invest on (isAvailableForStandardStrategy=true) and other asset such asset the
# bank account unit asset. Typicaly the market will price the value of the asset but we leave the posibility so specify a fix value
# (-1 is used to say it is not specified). We also allow us to modify the value (from the market or from the fix value) by multiply it by the scalar valueMultiplier
# asset are attached to a bet room
class Asset(models.Model):
	name = models.CharField(max_length=20, unique=True)
	betroom = models.ForeignKey(BetRoom) # betroom in which we can trade this asset
        isAvailableForStandardStrategy = models.BooleanField(default=True)
        valueMultiplier = models.FloatField(default=1)
        fixValue = models.FloatField(default=-1)
        isStandard = models.BooleanField(default=True) # false for the banck account and the bond.
        description = models.TextField()#description of what this asset is. What is the event associated to it, ...
        
	def __unicode__(self):
		return self.name +' (belongs to betroom '+self.betroom.name+')'


#-----------------------------------------------------------------------------------------------
# Strategy : repartition of the asset (percentage of the number of actions)
class Strategy(models.Model):
        betroom = models.ForeignKey(BetRoom) # betroom in which we can trade this asset
	name = models.CharField(max_length=20, unique=True)
	#the sum of all assetPercentagesInStrat must be 1
	assetPercentagesInStrat = models.ManyToManyField(Asset, through='AssetPercentageInStrategy')

	def __unicode__(self):
		return self.name +" (in "+ self.betroom.name + ")"

#-----------------------------------------------------------------------------------------------
#  The player is the representation of a celibax in a specific betroom
#  An account holder can be skyzophrnic and have many players (but no more than one in a given betroom)
#  The player has a main strategy and a list of funds.
#  he is linked to the betroom throught his main strategy 
class Player(models.Model):
	userBeingThisPlayer = models.ForeignKey(User) # account holder in charge of this player
	mainStrategy = models.ForeignKey(Strategy) # main strategy of the player

        
	def __unicode__(self):
		return self.userBeingThisPlayer.username+ " in " + self.mainStrategy.betroom.name
	


#-----------------------------------------------------------------------------------------------
# PercentatgeInStrat is a relationship between a Strategy and all his assets (formerly Proability)
# PLease not that the percentage are in number of shares (compared to a MtM percentage)
# the sum of all percentage in AssetPercentatgeInStrat for a strategy muste sum to 1
class AssetPercentageInStrategy(models.Model):
	asset = models.ForeignKey(Asset)
	strategy = models.ForeignKey(Strategy)
	# the percentage is user-defined and is an float
	assetPercentageInStrategy = models.FloatField()

	def __init__(self, *args, **kwargs):
                super(AssetPercentageInStrategy, self).__init__(*args, **kwargs)
                try:
                        test1 = (self.asset.betroom == self.strategy.betroom )
                        eps =  0.000000000001
                        test2 = (self.assetPercentageInStrategy <= 1.+eps1 and self.assetPercentageInStrategy >= -eps)    
                except:
                        test1 = True
                        test2 = True
                        pass
                if not(test1):
                        raise Exception("AssetPercentageInStrategy: betroom is not the same for the asset and the strat")
                if not(test2):
                        raise Exception("AssetPercentageInStrategy: assetPercentageInStrategy has to be between 0 and 1 " +self.asset.name+ " "+ str(self.assetPercentageInStrategy) )
                        

	def __unicode__(self):
		return self.asset.name+ " is "  +  str( 100*self.assetPercentageInStrategy )+ " percent of " +  self.strategy.name+ ' (in '+self.asset.betroom.name+')'


#-----------------------------------------------------------------------------------------------
# a fund is a number of asset associated to a strategy
# PercentatgeOnStrat is a relationship between a player portfolio and the fund.
# please note than the fundTargetPercentageInPortfolio are expressed in term of MtM percentage.
# they are set by the player and are his target but the true MtM percentage evolve with the market.
# the player has to keep the percentageof the fund invested in his main strategy gretaer than 50%
# when paying a divividend to a player who has not opened a bank accout yet, 1% of the percentage in the main fund
# will be affected to the bank account and this can lead to 49% as target MtM percentage.
# each time the player change his strategie, the fund are rebalanced so that the current MtM percentage match the target percentage
class Fund(models.Model):
        player = models.ForeignKey(Player)#the owner of the fund
	strategy = models.ForeignKey(Strategy)
	numberOfAssetsInFund = models.FloatField(default=0)
	#the sum of all assetPercentagesInStrat must be 1
	fundTargetPercentageInPortfolio = models.FloatField(default=1.)#this is as last set by the user
	
        def __init__(self, *args, **kwargs):
                super(Fund, self).__init__(*args, **kwargs)
                try:
                        test1 = (self.player.mainStrategy.betroom == self.strategy.betroom)
                        test2 = (self.fundTargetPercentageInPortfolio <= 1 and self.fundTargetPercentageInPortfolio >= 0)
                except:
                        test1 = True
                        test2 = True
                        pass
                if not(test1):
                        raise Exception("AssetPercentageInStrategy: betroom is not the same for the asset and the strat")
                if not(test2):
                        raise Exception("Fund: fundTargetPercentageInPortfolio has to be between 0 and 1")
                
                
	def __unicode__(self):
		return self.player.userBeingThisPlayer.username+" invested in: " + self.strategy.name + " with " + str( self.numberOfAssetsInFund ) +" assets. The target is " + str(100*self.fundTargetPercentageInPortfolio) + " in " + self.player.mainStrategy.betroom.name




###############################################################################################
#
#                       HITORIES

###############################################################################################
# An history is the picture of the market in one betroom at a specific time. We take this picture each time a player perform an action
# The following comment may be not out of date:
# we are currently storing 3 main informations 
# - asset prices funtion of time (nAsset curves)
# - player main strategy function of time (nPlayer curves)
# - player portfolio MtM function of time (nPlayer curves)
# this should be used as in: http://stackoverflow.com/questions/4487975/historicity-in-django
class History(models.Model):
	time = models.DateTimeField(default=datetime.now)
	# please note that registring the player avoid to register the betroom
	bettingPlayerName = models.CharField(max_length=20)# an history is stored on an action. This is the name of the player who started the action
	# we do not have a ForeignKey because playerPortfolios is already a link to Player
	betroom = models.ForeignKey(BetRoom) # bet room in which the history is recorded
	actionTaken = models.CharField(max_length=20) # this is a short name of describing th action that has been taken when storuing this history

        def __unicode__(self):
		return str(self.time)+ ", "+ self.bettingPlayerName + " " + self.actionTaken + " in " + self.betroom.name

#-----------------------------------------------------------------------------------------------
# PercentatgeInStrat is a relationship between a Strategy and all his assets (formerly Proability)
# the sum of all percentage in AssetPercentatgeInStrat for a strategy muste sum to 1
class AssetForHistory(models.Model):
	asset = models.ForeignKey(Asset)
	history = models.ForeignKey(History)
	# the percentage is user-defined and is an float
	price = models.FloatField()

	def __unicode__(self):
		return self.asset.name + " costs "+str( self.price ) + " Chochoros the "+ str( self.history.time ) + " in " + self.history.betroom.name

#-----------------------------------------------------------------------------------------------
# PlayerStrategyMtMForHistory. This class is only use for history purposes
class StrategyForHistory(models.Model):
	strategy = models.ForeignKey(Strategy)
	history = models.ForeignKey(History)
	strategyMtM = models.FloatField()
	strategyPerfSinceLastHistoryDate = models.FloatField()
	numberOfPlayersInvestedInTheStrategy = models.IntegerField()
	totalNumberOfAssetInvestedInTheStrategy = models.FloatField()

	def __unicode__(self):
		return self.strategy.name+ " is worthing "+ str( self.strategyMtM ) + " Chochoros the "+ str( self.history.time ) + " in " + self.history.betroom.name

#-----------------------------------------------------------------------------------------------
# PlayerPortfolioMtMForHistory. This class is only use for history purposes
class PlayerPortfolioForHistory(models.Model):
	player = models.ForeignKey(Player)
	history = models.ForeignKey(History)
	portfolioMtM = models.FloatField()
	investementInMainStrategyPercentage = models.FloatField()
	numberOfStrategies = models.IntegerField()

	def __unicode__(self):
                tmp = 'In ' + self.player.mainStrategy.betroom.name +', '  + self.player.userBeingThisPlayer.username + " is invested in "
                return tmp + str(self.numberOfStrategies) + " strat(s), for a total of "+ str(self.portfolioMtM)+" Chochoros the "+ str( self.history.time)


###############################################################################################
#
#                       MARKET BY CONTRACTS

###############################################################################################

#-----------------------------------------------------------------------------------------------
# The digital event has a expiry, and pays payoff if the associated event has hapens at expiry or nothing otherwise
class DigitalContract(models.Model):
        issuer = models.ForeignKey(Player) # Issuer of the contract, allow to acces the bet room in which the the contract can be traded
        name = models.CharField(max_length=20, unique=True)
        description = models.TextField()#description of what the event related to this contract
        # after the expiry, the contract should be fixed. It would only be settled once the (zero?) payments are made.
        # it can fix prior to expiry, as in the following exemple: "A happens before B". As soon as A or B happens, the contract is fixed
        # once the fixing has occured, nothing prevents to make an early settlement.
        # Once the contract has fixed, it is not tradable any longuer but the admin can wait to settlle it.
        # It may not be clear at expiry either the settlement should be done with a payment or no peyment. It that latter case (which should
        # should be avoided as often as possible by a clear definition of the contract), the settlement can be a repayment of all the premium paid on
        # the contract
        expiry = models.DateTimeField()
        # -2: not fixed, -1: fixed but not settled yet, 0: settled with no payment, 1: settled with payment, 2: settled with refund
        settlementStatus = models.IntegerField(default=-2)
        
        def __unicode__(self):
		return self.name + ' is a contract issued by ' +  self.issuer.userBeingThisPlayer.username + ' in ' + self.issuer.mainStrategy.betroom.name


#-----------------------------------------------------------------------------------------------
# This is a bid on a digital contract. The price should be between 0 and 100
class BidOnContract(models.Model):
        contract = models.ForeignKey(DigitalContract)
        player = models.ForeignKey(Player)
        price = models.IntegerField()# price are interger between 0 and 100

        def __unicode__(self):
		return 'Bid on ' +  self.contract.name + ' at ' + str(self.price) + ' from ' + self.player.userBeingThisPlayer.username +' in ' + self.player.mainStrategy.betroom.name


#-----------------------------------------------------------------------------------------------
# This is an offer on a digital contract. The price should be between 0 and 100
class OfferOnContract(models.Model):
        contract = models.ForeignKey(DigitalContract)
        player = models.ForeignKey(Player)
        price = models.IntegerField()# price are interger between 0 and 100

        def __unicode__(self):
		return 'Offer on ' +  self.contract.name + ' at ' + str(self.price) + ' from ' + self.player.userBeingThisPlayer.username +' in ' + self.player.mainStrategy.betroom.name

#-----------------------------------------------------------------------------------------------
# The liquidity token is a duty to offer a tight bid/ask spread. Once the player hilding the token sell or buy a contract,
# the token is transfered to the contreparty and the first player is relieved from his duty.
class LiquidityToken(models.Model):
        offer = models.ForeignKey(OfferOnContract)
        bid = models.ForeignKey(BidOnContract)

        def __init__(self, *args, **kwargs):
                super(LiquidityToken, self).__init__(*args, **kwargs)
                if self.offer.contract != self.bid.contract:
                        raise Exception("LiquidityToken: bid and offer should be on the same contract.")
                if self.offer.player != self.bid.player:
                        raise Exception("LiquidityToken: bid and offer should be the order of a same player.")

        def __unicode__(self):
		return 'Liquidity Token on  ' +  self.offer.contract.name



#-----------------------------------------------------------------------------------------------
# This a traded contract. When the deal is settled after the expiry on the contract, 100 or 0 chochoros are transfered from the
# account of the seller to the account of the buyer
class TradedContract(models.Model):
        contract = models.ForeignKey(DigitalContract)
        buyer = models.ForeignKey(Player, related_name='buyer')
        seller = models.ForeignKey(Player, related_name='seller')
        tradeDate = models.DateTimeField(default=datetime.now)
        tradedPrice = models.IntegerField()# price are interger between 0 and 100


#-----------------------------------------------------------------------------------------------
# Store which contract the player has visited last
class LastVisitedContract(models.Model):
        player = models.OneToOneField(Player, primary_key=True)
        contract = models.ForeignKey(DigitalContract)

        def __unicode__(self):
		return self.player.userBeingThisPlayer.username+ ' has last visited  ' +  self.contract.name

###############################################################################################
#
#                       CHAT/COMMENTS

###############################################################################################
# This class stores a comment posted via the website
class WebSiteComment(models.Model):
	postTime = models.DateTimeField(default=datetime.now)
	poster = models.ForeignKey(Player)# please note that registring the player avoid to register the betroom
	textValue = models.TextField()
	isVisible = models.BooleanField(default=True)

	def __unicode__(self):
		return self.poster.userBeingThisPlayer.username + ' says ' +  self.textValue


###############################################################################################
#
#                       COMET

###############################################################################################
# This class represent the "up to date" status for the market of a player
class MarketPlacePlayerMarketUpToDateStatus(models.Model):
	player = models.ForeignKey(Player)# please note that registring the player avoid to register the betroom
	isUpToDateStatusForTheMarket = models.BooleanField(default=True)

	def __unicode__(self):
                tmp =''
                if not(self.isUpToDateStatusForTheMarket):
                        tmp = 'not '
		return self.player.userBeingThisPlayer.username+' is '+tmp+ ' up to date for the market in '+ self.player.mainStrategy.betroom.name
	
	
#-----------------------------------------------------------------------------------------------
# This class represent the "up to date" for the chat status of a player
class ChatRoomPlayerUpToDateStatus(models.Model):
	player = models.ForeignKey(Player)# please note that registring the player avoid to register the betroom
	isUpToDateStatusForTheChat = models.BooleanField(default=True)

	def __unicode__(self):
                tmp =''
                if not(self.isUpToDateStatusForTheChat):
                        tmp = 'not '
		return self.player.userBeingThisPlayer.username +' is '+tmp+ ' up to date for the chat in '+ self.player.mainStrategy.betroom.name
	

